using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using ZoneFiveSoftware.Common.Data;
using ZoneFiveSoftware.Common.Data.Fitness;
using ZoneFiveSoftware.Common.Data.Measurement;
using FilteredStatistics.Common;
using FilteredStatistics.Common.Controller;
using FilteredStatistics.Controller;
using FilteredStatistics.View;

namespace FilteredStatistics.Data
{
    class TemplateCustomDistanceLapsFilterCriteria : FilterCriteriaFacade, ISplitsCriteria
    {
        public TemplateCustomDistanceLapsFilterCriteria(float maxDistance,
                                                        float distance,
                                                        float startOffset,
                                                        Length.Units distanceUnit)
        {
            m_ActivityCallbackHelper = new ActivityDataChangedHelper(null);
            m_ActivityCallbackHelper.PropertyChanged += new PropertyChangedEventHandler(OnActivityDataChanged);

            Init(maxDistance, distance, startOffset, distanceUnit);
        }

#region FilterCriteriaFacade members

        public override Guid ReferenceId
        {
            get { return GUIDs.TemplateSplitsCriteriaGuid; }
        }

        public override IActivity Activity
        {
            set
            {
                m_Activity = value;
                m_ActivityCallbackHelper.Activity = m_Activity;

                foreach (DistanceLapNamedZone zone in ConcreteNamedZones)
                {
                    zone.Activity = value;
                }
            }
        }

        public override String DisplayName
        {
            get { return PluginView.GetLocalizedString("CustomDistanceLapsText"); }
        }
        
        public override bool IsTemplateOnly
        {
            get { return true; }
        }

        public override bool IsMainViewOnly
        {
            get { return false; }
        }

        public override List<object> NamedZones
        {
            get { return m_SplitsNamedZones; }
        }

        public override bool IsSerializable
        {
            get { return true; }
        }

        public override UInt16 DataVersion
        {
            get { return Constants.CurrentVersion.VersionNumber; }
        }

        public override void SerializeCriteria(Stream stream)
        {
            Serialize(stream);
        }

        public override object DeserializeCriteria(Stream stream, UInt16 version)
        {
            Deserialize(stream, new DataVersion(version));

            return this;
        }

        public override object TemplateCompatibleCriteria
        {
            get
            {
                return new TemplateCustomDistanceLapsFilterCriteria(m_MaxDistance, m_LapDistance,
                                                                    m_StartOffset, m_DistanceUnit);
            }
        }

#endregion

#region ISplitsCriteria Members

        public SplitsCriteriaType SplitsType
        {
            get { return SplitsCriteriaType.CustomDistance; }
        }

#endregion

#region IPluginSerializable members

        public override void Serialize(Stream stream)
        {
            base.Serialize(stream);

            SerializableInt32Range splitsType = new SerializableInt32Range((Int32)SplitsCriteriaType.CustomDistance);
            SerializableInt32Range distanceUnit = new SerializableInt32Range((Int32)m_DistanceUnit);
            SerializableDoubleRange maxDistance = new SerializableDoubleRange(m_MaxDistance);
            SerializableDoubleRange distance = new SerializableDoubleRange(m_LapDistance);
            SerializableDoubleRange offset = new SerializableDoubleRange(m_StartOffset);

            splitsType.Serialize(stream);
            distanceUnit.Serialize(stream);
            maxDistance.Serialize(stream);
            distance.Serialize(stream);
            offset.Serialize(stream);
        }

#endregion

#region Deserialization methods

        public void Deserialize_V1(Stream stream, DataVersion version)
        {
            SerializableInt32Range distanceUnit = new SerializableInt32Range((Int32)m_DistanceUnit);
            SerializableDoubleRange maxDistance = new SerializableDoubleRange(m_MaxDistance);
            SerializableDoubleRange distance = new SerializableDoubleRange(m_LapDistance);

            distanceUnit.Deserialize(stream, version);
            maxDistance.Deserialize(stream, version);
            distance.Deserialize(stream, version);

            Init((float)maxDistance, (float)distance, 0, (Length.Units)(Int32)distanceUnit);
        }

        public void Deserialize_V4(Stream stream, DataVersion version)
        {
            SerializableInt32Range distanceUnit = new SerializableInt32Range((Int32)m_DistanceUnit);
            SerializableDoubleRange maxDistance = new SerializableDoubleRange(m_MaxDistance);
            SerializableDoubleRange distance = new SerializableDoubleRange(m_LapDistance);
            SerializableDoubleRange offset = new SerializableDoubleRange(m_StartOffset);

            distanceUnit.Deserialize(stream, version);
            maxDistance.Deserialize(stream, version);
            distance.Deserialize(stream, version);
            offset.Deserialize(stream, version);

            Init((float)maxDistance, (float)distance,
                 (float)offset, (Length.Units)(Int32)distanceUnit);
        }
#endregion


        private void OnActivityDataChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Activity.Duration")
            {
                CreateSplitsNamedZonesList();
            }
        }

        private void CreateSplitsNamedZonesList()
        {
            float lapStartDistance = m_StartOffset;
            float lapEndDistance = lapStartDistance + m_LapDistance;
            int lapIndex = 0;

            // Create/update a named zone for each distance interval
            while (lapStartDistance < m_MaxDistance)
            {
                DistanceLapNamedZone currentNamedZone;

                if(m_SplitsNamedZones.Count > lapIndex)
                {
                    // Reuse the already created named zone
                    currentNamedZone = m_SplitsNamedZones[lapIndex] as DistanceLapNamedZone;
                    currentNamedZone.Init(m_Activity, lapStartDistance, Math.Min(m_MaxDistance, lapEndDistance), m_DistanceUnit);
                }
                else
                {
                    // Create a new one
                    currentNamedZone = new DistanceLapNamedZone(m_Activity, lapIndex, lapStartDistance, Math.Min(m_MaxDistance, lapEndDistance), m_DistanceUnit);
                    m_SplitsNamedZones.Add(currentNamedZone);
                }

                lapStartDistance = lapEndDistance;
                lapEndDistance += m_LapDistance;
                lapIndex++;
            }

            // Remove excess named zones if required
            if (lapIndex < m_SplitsNamedZones.Count)
            {
                m_SplitsNamedZones.RemoveRange(lapIndex, m_SplitsNamedZones.Count - lapIndex);
            }

            TriggerNamedZonesListChanged();
        }

        public void Init(float maxDistance, float distance,
                         float offset, Length.Units distanceUnit)
        {
            m_MaxDistance = maxDistance;
            m_LapDistance = distance;
            m_DistanceUnit = distanceUnit;
            m_StartOffset = offset;

            CreateSplitsNamedZonesList();
        }

        public float LapDistance
        {
            get { return m_LapDistance; }
        }

        public float MaximumDistance
        {
            get { return m_MaxDistance; }
        }

        public Length.Units DistanceUnits
        {
            get { return m_DistanceUnit; }
        }

        public float StartOffset
        {
            get { return m_StartOffset; }
        }

        private IActivity m_Activity = null;
        private ActivityDataChangedHelper m_ActivityCallbackHelper = null;
        private float m_MaxDistance;
        private float m_LapDistance;
        private float m_StartOffset;
        private Length.Units m_DistanceUnit;
        private List<object> m_SplitsNamedZones = new List<object>();
    }
}
